/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/**************************************************************************/
/*                 Generic Graphic source file                            */
/*                                                                        */
/*         Adapted by Zeograd (Olivier Jolly) for using Allegro           */
/*                                                                        */
/**************************************************************************/

#include "pce.h"
#include "utils.h"
#include "config.h"

typedef struct {
  UChar r,g,b;
} rgb_map_struct;

rgb_map_struct rgb_map[256];

void SetPalette(void)
{
  UChar i;

  osd_gfx_set_color(255, 0x3f, 0x3f, 0x3f);
  rgb_map[255].r = 255;
  rgb_map[255].b = 255;
  rgb_map[255].g = 255;

  for (i = 0; i < 255; i++)
    {
      osd_gfx_set_color(i, (i & 0x1C) << 1, (i & 0xe0) >> 2, (i & 0x03) << 4);
      rgb_map[i].r = (i & 0x1C) << 3;
      rgb_map[i].g = (i & 0xe0);
      rgb_map[i].b = (i & 0x03) << 6;
    }
#if defined(SDL)

  olay_cmap[255].r = (Uint8) ((0.299 * 0xff) + (0.587 * 0xff) + (0.114 * 0xff));
  olay_cmap[255].g = (Uint8) ((0xff - olay_cmap[i].r) * 0.565 + 128);
  olay_cmap[255].b = (Uint8) ((0xff - olay_cmap[i].r) * 0.713 + 128);

  for (i = 0; i < 255; i++)
    {
      int r,g,b;
      r = (i & 0x1C) << 3;
      g = (i & 0xe0);
      b = (i & 0x03) << 6;
      olay_cmap[i].r = (Uint8)((0.299 * r) + (0.587 * g) + (0.114 * b));
      olay_cmap[i].g = (Uint8)((b - olay_cmap[i].r) * 0.565 + 128);
      olay_cmap[i].b = (Uint8)((r - olay_cmap[i].r) * 0.713 + 128);
    }

#endif
}


/*!
 * calc_fullscreen_aspect:
 * Generic utility that takes the width x height of the current output screen
 * and sets up a gfx lib independent struct generic_rect (gfx.h) with the
 * aspect-correct scaling values, provided option.want_fullscreen_aspect
 * has been set in the config or via the command line.
 */
void calc_fullscreen_aspect(unsigned short physical_screen_width,
                            unsigned short physical_screen_height,
                            struct generic_rect *rect,
                            unsigned short pce_screen_width,
                            unsigned short pce_screen_height)
{
  /*
   * Routine not called often so extra sanity checks pose no penalty.
   */
  if (physical_screen_height == 0)
    {
      printf("calc_fullscreen_aspect: physical_screen_height is 0!  Aborting . . .\n");
      exit(0);
    }

  if (pce_screen_width == 0)
    {
      printf("calc_fullscreen_aspect: pce_screen_width is 0!  Aborting . . .\n");
      exit(0);
    }

  if (option.want_fullscreen_aspect)
    {
      float physical_screen_ratio, pce_ratio;
      int new_size;

      physical_screen_ratio = (float) physical_screen_width / physical_screen_height;

      pce_ratio = (pce_screen_width / physical_screen_ratio) / pce_screen_height;

      if (pce_ratio < 1.0)
        {
          new_size = (int)(physical_screen_width * pce_ratio);

          (*rect).start_x = (unsigned short int)((physical_screen_width - new_size) / 2);
          (*rect).start_y = 0;
          (*rect).end_x = (unsigned short int)new_size;
          (*rect).end_y = physical_screen_height;
        }
      else
        {
          new_size = (int)(physical_screen_height / pce_ratio);

          (*rect).start_x = 0;
          (*rect).start_y = (unsigned short int)((physical_screen_height - new_size) / 2);
          (*rect).end_x = physical_screen_width;
          (*rect).end_y = (unsigned short int)new_size;
        }
    }
  else
    {
      (*rect).start_x = (*rect).start_y = 0;
      (*rect).end_x = physical_screen_width;
      (*rect).end_y = physical_screen_height;
    }
}

//! Computes the new screen height and eventually change the screen mode
void change_pce_screen_height()
{
  //! minimal theorical line where to begin drawing
  int min_display;

  //! maximal theorical line where to end drawing
  int max_display;

  int cur_display;

  int temp_vds = io.VDC[VPR].B.h;
  int temp_vsw = io.VDC[VPR].B.l;
  int temp_vdw = (int)io.VDC[VDW].W;
  int temp_vcr = (int)io.VDC[VCR].W;

#if defined(GFX_DEBUG)
  printf("Changing pce screen mode\nVDS = %04x VSW = %04x VDW = %04x VCR = %04x\n",
         temp_vds,
         temp_vsw,
         temp_vdw,
         temp_vcr
         );
  // getchar();
#endif

  if (temp_vdw == 0)
    return;

  min_display = temp_vds + temp_vsw;

  max_display = cur_display = min_display;
  while (cur_display < 242 + 14)
    {
      cur_display += temp_vdw;
      max_display = cur_display;

      cur_display += 3 + temp_vcr;

#if defined(GFX_DEBUG)
      printf("Adding vdw to the height of graphics, cur_display = %d\n", cur_display);
#endif
    }

  min_display = (min_display > 14 ? min_display : 14);
  max_display = (max_display < 242 + 14 ? max_display : 242 + 14);


  /*
  if (option.want_television_size_emulation)
    {
      if (max_display - min_display >= 224)
        {
          int offset;

          offset = (max_display - min_display + 1 - 224) / 2;

          min_display += offset;
          max_display = min_display + 223;
        }
    }
  */

  io.vdc_min_display = (UInt16)min_display;
  io.vdc_max_display = (UInt16)max_display;

#if defined(GFX_DEBUG)
  // printf("min_display = %d\tmax_display = %d\n", min_display, max_display);
#endif

  //! Number of lines to render
  io.screen_h = max_display - min_display + 1;

#if defined(GFX_DEBUG)
  // printf("%d lines to render\n", io.screen_h);
#endif

  (*osd_gfx_driver_list[video_driver].mode) ();
}

#if defined(NEW_GFX_ENGINE)

#define MAX_GFX_CONTEXT_SLOT_NUMBER 2

static gfx_context saved_gfx_context[MAX_GFX_CONTEXT_SLOT_NUMBER];

//! Whether we need to draw pending lines
static int gfx_need_redraw;

//! Frame to skip before the next frame to render
static int UCount = 0;

//! Whether we should change video mode after drawing the current frame
int gfx_need_video_mode_change = 0;

void
save_gfx_context(int slot_number)
{

  gfx_context *destination_context;

  destination_context = saved_gfx_context + slot_number;

  if (slot_number == 0)
    {
      /*
      if ((destination_context->scroll_x == ScrollX) &&
          (destination_context->scroll_y == ScrollY) &&
          (destination_context->scroll_y_diff == ScrollYDiff) &&
          (destination_context->cr == io.VDC[CR].W))
        {
#if defined(GFX_DEBUG)
          gfx_debug_printf("Canceled primary context saving, nothing changed");
#endif
          return;
        }
      */

      if (gfx_need_redraw == 0)
        gfx_need_redraw = 1;
      else // Context is already saved and we haven't render the lines using it
        {
#if defined(GFX_DEBUG)
          gfx_debug_printf("Canceled context saving as a previous one wasn't consumed yet");
#endif
          return;
        }
    }
  if (slot_number >= MAX_GFX_CONTEXT_SLOT_NUMBER)
    {
      Log("Internal error in %s(%s), slot %d >= %d\n",
          __FUNCTION__,
          __FILE__,
          slot_number,
          MAX_GFX_CONTEXT_SLOT_NUMBER
          );

      return;
    }

#if defined(GFX_DEBUG)
  gfx_debug_printf("Saving context %d, scroll = (%d,%d,%d), CR = 0x%02d",
                   slot_number,
                   ScrollX,
                   ScrollY,
                   ScrollYDiff,
                   io.VDC[CR].W);
#endif

  destination_context->scroll_x = ScrollX;
  destination_context->scroll_y = ScrollY;
  destination_context->scroll_y_diff = ScrollYDiff;
  destination_context->cr = io.VDC[CR].W;
}

void
load_gfx_context(int slot_number)
{

  gfx_context *source_context;

  if (slot_number >= MAX_GFX_CONTEXT_SLOT_NUMBER)
    {
      Log("Internal error in %s(%s), slot %d >= %d\n",
          __FUNCTION__,
          __FILE__,
          slot_number,
          MAX_GFX_CONTEXT_SLOT_NUMBER
          );

      return;
    }

  source_context = saved_gfx_context + slot_number;

  ScrollX = source_context->scroll_x;
  ScrollY = source_context->scroll_y;
  ScrollYDiff = source_context->scroll_y_diff;
  io.VDC[CR].W = source_context->cr;

#if defined(GFX_DEBUG)
  gfx_debug_printf("Restoring context %d, scroll = (%d,%d,%d), CR = 0x%02d",
                   slot_number,
                   ScrollX,
                   ScrollY,
                   ScrollYDiff,
                   io.VDC[CR].W);
#endif

}


//! render lines
/*
  render lines into the buffer from min_line to max_line, inclusive
        Refresh* draw things from min to max line given, with max exclusive
*/
void
render_lines(int min_line, int max_line)
{

#if !defined(FINAL_RELEASE)
  //printf("render lines %3d - %3d in %s\n", min_line, max_line, __FILE__);
#endif

  save_gfx_context(1);

  load_gfx_context(0);

  if (!UCount) // Either we're in frameskip = 0 or we're in the frame to draw
    {
      if (SpriteON && SPONSwitch)
        RefreshSpriteExact (min_line, max_line - 1, 0);

      RefreshLine (min_line, max_line - 1);

      if (SpriteON && SPONSwitch)
        RefreshSpriteExact (min_line, max_line - 1, 1);
    }

  load_gfx_context(1);

  gfx_need_redraw = 0;
}

//! Rewritten version of Loop6502 from scratch, called when each line drawing should occur
/* TODO:
   - sprite #0 collision checking (occur as soon as the sprite #0 is shown and overlap another sprite
   - frame skipping to test
*/
UChar
Loop6502()
{
  static int video_dump_countdown = 0;
  static int display_counter = 0;
  static int last_display_counter = 0;
  static int satb_dma_counter = 0;
  UChar return_value = INT_NONE;

  io.vdc_status &= ~(VDC_RasHit | VDC_SATBfinish);

  // Count dma delay

  if (satb_dma_counter > 0)
    {
      // A dma is in progress
      satb_dma_counter --;

      if (!satb_dma_counter)
        {
          // dma has just finished
          if (SATBIntON)
            {
              io.vdc_status |= VDC_SATBfinish;
              return_value = INT_IRQ;
            }
        }
    }

  // Test raster hit
  if (RasHitON)
    {
      if (((io.VDC[RCR].W & 0x3FF) >= 0x40) && ((io.VDC[RCR].W & 0x3FF) <= 0x146))
        {
          UInt16 temp_rcr = (UInt16)((io.VDC[RCR].W & 0x3FF) - 0x40);

          if (scanline == (temp_rcr + io.VDC[VPR].B.l + io.VDC[VPR].B.h) % 263)
            {
              // printf("\n---------------------\nRASTER HIT (%d)\n----------------------\n", scanline);
              io.vdc_status |= VDC_RasHit;
              return_value = INT_IRQ;
            }

        }
      else
        {
          // printf("Raster counter out of bounds (%d)\n", io.VDC[RCR].W);
        }
    }
  //  else
  //    printf("Raster disabled\n");

  // Rendering of tiles / sprites

  if (scanline < 14)
    {
      gfx_need_redraw = 0;
    }
  else if (scanline < 14 + 242)
    {

      if (scanline == 14)
        {

          last_display_counter = 0;
          display_counter = 0;
          ScrollYDiff = 0;
          oldScrollYDiff = 0;

          // Signal that we've left the VBlank area
          io.vdc_status &= ~VDC_InVBlank;
#if defined(GFX_DEBUG)
          gfx_debug_printf("Cleaning VBlank bit from vdc_status (now, 0x%02x)", io.vdc_status);
#endif
        }

      if (scanline == io.vdc_min_display)
        {
          gfx_need_redraw = 0;

          save_gfx_context(0);

#if defined(GFX_DEBUG) && !defined(FINAL_RELEASE)
          printf("FORCED SAVE OF GFX CONTEXT\n");
#endif

        }

      if ((scanline >= io.vdc_min_display) && (scanline <= io.vdc_max_display))
        {
          if (gfx_need_redraw) // && scanline > io.vdc_min_display) // We got render things before being on the second line
            {
              render_lines(last_display_counter, display_counter);
              last_display_counter = display_counter;
            }

          display_counter ++;
        }
    }
  else if (scanline < 14 + 242 + 4)
    {

      if (scanline == 14 + 242)
        {

          save_gfx_context(0);

          render_lines(last_display_counter, display_counter);

          if (video_dump_flag)
            {
              if (video_dump_countdown)
                video_dump_countdown--;
              else
                {
                  dump_video_frame();
                  video_dump_countdown = 3;
                }
            }

          if (gfx_need_video_mode_change)
            {
              gfx_need_video_mode_change = 0;
              change_pce_screen_height();
            }

          if (osd_keyboard ())
            return INT_QUIT;

          if (!UCount)
            RefreshScreen();

#if defined(GTK)
          /*@ -unrecog */
          while (gtk_events_pending())
	    {
	      if (gtk_main_iteration())
		{
		  return INT_QUIT;
		}
	    }
          /*@ =unrecog */
#endif
          /*@-preproc */
#warning "place this better"
          /*@=preproc */
          if (CheckSprites ())
            io.vdc_status |= VDC_SpHit;
          else
            io.vdc_status &= ~VDC_SpHit;

          if (!UCount)
            {
#if defined(ENABLE_NETPLAY)
              if (option.want_netplay != INTERNET_PROTOCOL)
                {
                  /* When in internet protocol mode, it's the server which is in charge of throlling */
                  wait_next_vsync();
                }
#else
              wait_next_vsync();
#endif /* NETPLAY_ENABLE */

              UCount = UPeriod;
            }
          else
            UCount--;

          /* VRAM to SATB DMA */
          if (io.vdc_satb == 1 || io.VDC[DCR].W & 0x0010)
            {
#if defined(WORDS_BIGENDIAN)
              swab(VRAM + io.VDC[SATB].W * 2, SPRAM, 64 * 8);
#else
              memcpy (SPRAM, VRAM + io.VDC[SATB].W * 2, 64 * 8);
#endif
              io.vdc_satb = 1;
              io.vdc_status &= ~VDC_SATBfinish;

              // Mark satb dma end interuption to happen in 4 scanlines
              satb_dma_counter = 4;
            }

          if (return_value == INT_IRQ)
            io.vdc_pendvsync = 1;
          else
            {
              if (VBlankON)
                {
                  io.vdc_status |= VDC_InVBlank;
                  return_value = INT_IRQ;
                }
            }
        }

    }
  else
    {
      //Three last lines of ntsc scanlining
    }

  // Incrementing the scanline

  scanline ++;

  if (scanline >= 263)
    scanline = 0;

  if ((return_value != INT_IRQ) && io.vdc_pendvsync)
    {
      io.vdc_status |= VDC_InVBlank;
      return_value = INT_IRQ;
      io.vdc_pendvsync = 0;
    }

  if (return_value == INT_IRQ)
    {
      if (!(io.irq_mask & IRQ1))
        {
          io.irq_status |= IRQ1;
          return return_value;
        }
    }

  return INT_NONE;
}
#endif

int video_dump_flag = 0;

//! Raw dump the current frame into the buffer, for video output purposes
void dump_uyvy_frame(char* output_buffer)
{
#ifndef NGC
  int x,y;
  UChar* xbuf_pointer;

  xbuf_pointer = osd_gfx_buffer;

  for (y = 0; y < io.screen_h; y++, xbuf_pointer += XBUF_WIDTH - io.screen_w)
    for (x = 0; x < io.screen_w - 1; x+=2, xbuf_pointer+=2)
      {
        *(output_buffer++) = (char)olay_cmap[*xbuf_pointer].g;
        *(output_buffer++) = (char)olay_cmap[*xbuf_pointer].r;
        *(output_buffer++) = (char)olay_cmap[*xbuf_pointer].b;
        *(output_buffer++) = (char)olay_cmap[*(xbuf_pointer+1)].r;
      }
#endif
}


//! Raw dump the current frame into the buffer, for video output purposes
void dump_yyuv_frame(char* output_buffer)
{
#ifndef NGC
  int x,y;
  UChar* xbuf_pointer;

  xbuf_pointer = osd_gfx_buffer;

  for (y = 0; y < io.screen_h; y++, xbuf_pointer += XBUF_WIDTH - io.screen_w)
    for (x = 0; x < io.screen_w - 1; x+=2, xbuf_pointer+=2)
      {
        *(output_buffer++) = (char)olay_cmap[*xbuf_pointer].r;
        *(output_buffer++) = (char)olay_cmap[*(xbuf_pointer+1)].r;
        *(output_buffer++) = (char)olay_cmap[*xbuf_pointer].g;
        *(output_buffer++) = (char)olay_cmap[*xbuf_pointer].b;
      }
#endif
}

//! Raw dump the current frame into the buffer, for video output purposes
void dump_rgb_frame(char* output_buffer)
{
  int x,y;
  UChar* xbuf_pointer;

  xbuf_pointer = osd_gfx_buffer;

  for (y = 0; y < io.screen_h; y++, xbuf_pointer += XBUF_WIDTH - io.screen_w)
    for (x = 0; x < io.screen_w; x++, xbuf_pointer++)
      {
        *(output_buffer++) = (char)rgb_map[*xbuf_pointer].r;
        *(output_buffer++) = (char)rgb_map[*xbuf_pointer].g;
        *(output_buffer++) = (char)rgb_map[*xbuf_pointer].b;
      }
}

//! Raw dump the current frame into the buffer, for video output purposes
void dump_raw_frame(char* output_buffer)
{
  int y;
  UChar* xbuf_pointer;

  xbuf_pointer = osd_gfx_buffer;

  for (y = 0; y < (io.screen_h & 0xFFFE); y++, xbuf_pointer += XBUF_WIDTH, output_buffer += (io.screen_w & 0xFFFE))
    memcpy(output_buffer, xbuf_pointer, (size_t)(io.screen_w & 0xFFFE));
}


//! Raw dump the current frame into the buffers, for video output purposes
void dump_uyv_frame_separated(char* output_buffer_u, char* output_buffer_y, char* output_buffer_v)
{
#ifndef NGC
  int x,y;
  UChar* xbuf_pointer;

  xbuf_pointer = osd_gfx_buffer;

  for (y = 0; y < (io.screen_h & 0xFFFE); y++, xbuf_pointer += XBUF_WIDTH - io.screen_w)
    for (x = 0; x < io.screen_w - 1; x+=2, xbuf_pointer+=2)
      {
        *(output_buffer_u++) = (char)olay_cmap[*xbuf_pointer].g;
        *(output_buffer_y++) = (char)olay_cmap[*xbuf_pointer].r;
        *(output_buffer_v++) = (char)olay_cmap[*xbuf_pointer].b;
        *(output_buffer_y++) = (char)olay_cmap[*(xbuf_pointer+1)].r;
      }
#endif
}

#if defined(DUMP_UYVY_SINGLE_FILE)

static FILE* video_output_file = NULL;

//! Start the video dump process
//! return 1 if video dumping began, else 0
int start_dump_video()
{
  char video_output_filename[PATH_MAX];
  struct tm * tm_current_time;
  time_t time_t_current_time;

  if (video_output_file != NULL)
    return 0;

  time(&time_t_current_time);
  tm_current_time = localtime(&time_t_current_time);

  snprintf(video_output_filename, PATH_MAX, "%svideo-%04d-%02d-%02d %02d-%02d-%02d",
           video_path,
           tm_current_time->tm_year + 1900,
           tm_current_time->tm_mon,
           tm_current_time->tm_mday,
           tm_current_time->tm_hour,
           tm_current_time->tm_min,
           tm_current_time->tm_sec);

  video_output_file = fopen(video_output_filename, "wb");

  return (video_output_file != NULL ? 1 : 0);
}

//! Dump the current frame into the video file
void dump_video_frame()
{
  char* frame_buffer;

  if (video_output_file == NULL)
    return;

  frame_buffer = malloc(4 * io.screen_w * io.screen_h);

  if (frame_buffer == NULL)
    return;

  dump_uyvy_frame(frame_buffer);

  fwrite(frame_buffer, 4 * io.screen_w * io.screen_h, 1, video_output_file);

  free(frame_buffer);
}

//! Stop the dump video process
void stop_dump_video()
{
  if (video_output_file != NULL)
    fclose(video_output_file);

  printf("Video dump finished on a %dx%d resolution\n", io.screen_w, io.screen_h & 0xFFFE);
}

#elif defined(DUMP_UYVY_MULTI_FILE)

static char video_output_base_filename[PATH_MAX];
static int video_output_frame_count;

//! Start the video dump process
//! return 1 if video dumping began, else 0
int start_dump_video()
{
  struct tm * tm_current_time;
  time_t time_t_current_time;

  if (video_dump_flag)
    return 0;

  time(&time_t_current_time);
  tm_current_time = localtime(&time_t_current_time);

  snprintf(video_output_base_filename, PATH_MAX, "%svideo-%04d-%02d-%02d %02d-%02d-%02d",
           video_path,
           tm_current_time->tm_year + 1900,
           tm_current_time->tm_mon,
           tm_current_time->tm_mday,
           tm_current_time->tm_hour,
           tm_current_time->tm_min,
           tm_current_time->tm_sec);

  video_output_frame_count = 0;

  // We can't do much in this case, as we don't open a global handle for dumping
  return 1;
}

//! Dump the current frame into the video file
void dump_video_frame()
{
  char *frame_buffer_u;
  char *frame_buffer_v;
  char *frame_buffer_y;

  char video_output_filename_u[PATH_MAX];
  char video_output_filename_v[PATH_MAX];
  char video_output_filename_y[PATH_MAX];

  FILE* tmp_video_output_file;

  snprintf(video_output_filename_u, PATH_MAX, "%s-%d.U",
           video_output_base_filename,
           video_output_frame_count);

  snprintf(video_output_filename_v, PATH_MAX, "%s-%d.V",
           video_output_base_filename,
           video_output_frame_count);

  snprintf(video_output_filename_y, PATH_MAX, "%s-%d.Y",
           video_output_base_filename,
           video_output_frame_count);

  frame_buffer_u = malloc((io.screen_w & 0xFFFE) * (io.screen_h & 0xFFFE));
  frame_buffer_v = malloc((io.screen_w & 0xFFFE) * (io.screen_h & 0xFFFE));
  frame_buffer_y = malloc(2 * (io.screen_w & 0xFFFE) * (io.screen_h & 0xFFFE));

  if ((frame_buffer_u == NULL) || (frame_buffer_v == NULL) || (frame_buffer_y == NULL))
    return;

  dump_uyv_frame_separated(frame_buffer_u, frame_buffer_y, frame_buffer_v);

  tmp_video_output_file = fopen(video_output_filename_u, "wb");
  if (tmp_video_output_file != NULL)
    {
      fwrite(frame_buffer_u, (io.screen_w & 0xFFFE) * (io.screen_h & 0xFFFE), 1, tmp_video_output_file);
      fclose(tmp_video_output_file);
    }

  tmp_video_output_file = fopen(video_output_filename_y, "wb");
  if (tmp_video_output_file != NULL)
    {
      fwrite(frame_buffer_y, 2 * (io.screen_w & 0xFFFE) * (io.screen_h & 0xFFFE), 1, tmp_video_output_file);
      fclose(tmp_video_output_file);
    }

  tmp_video_output_file = fopen(video_output_filename_v, "wb");
  if (tmp_video_output_file != NULL)
    {
      fwrite(frame_buffer_v,(io.screen_w & 0xFFFE) * (io.screen_h & 0xFFFE), 1, tmp_video_output_file);
      fclose(tmp_video_output_file);
    }

  video_output_frame_count ++;

  free(frame_buffer_u);
  free(frame_buffer_v);
  free(frame_buffer_y);
}

//! Stop the dump video process
void stop_dump_video()
{
  printf("Video dump finished on a %dx%d resolution\n", io.screen_w & 0xFFFE, io.screen_h & 0xFFFE);
}

#elif defined(DUMP_YYUV_SINGLE_FILE)

static FILE* video_output_file = NULL;

//! Start the video dump process
//! return 1 if video dumping began, else 0
int start_dump_video()
{
  char video_output_filename[PATH_MAX];
  struct tm * tm_current_time;
  time_t time_t_current_time;

  if (video_output_file != NULL)
    return 0;

  time(&time_t_current_time);
  tm_current_time = localtime(&time_t_current_time);

  snprintf(video_output_filename, PATH_MAX, "%svideo-%04d-%02d-%02d %02d-%02d-%02d",
           video_path,
           tm_current_time->tm_year + 1900,
           tm_current_time->tm_mon,
           tm_current_time->tm_mday,
           tm_current_time->tm_hour,
           tm_current_time->tm_min,
           tm_current_time->tm_sec);

  video_output_file = fopen(video_output_filename, "wb");

  return (video_output_file != NULL ? 1 : 0);
}

//! Dump the current frame into the video file
void dump_video_frame()
{
  char* frame_buffer;

  if (video_output_file == NULL)
    return;

  frame_buffer = malloc(4 * io.screen_w * io.screen_h);

  if (frame_buffer == NULL)
    return;

  dump_yyuv_frame(frame_buffer);

  fwrite(frame_buffer, 4 * io.screen_w * io.screen_h, 1, video_output_file);

  free(frame_buffer);
}

//! Stop the dump video process
void stop_dump_video()
{
  if (video_output_file != NULL)
    fclose(video_output_file);

  printf("Video dump finished on a %dx%d resolution\n", io.screen_w, io.screen_h & 0xFFFE);
}

#elif defined(DUMP_PPM_MULTI_FILE)

static char video_output_base_filename[PATH_MAX];
static int video_output_frame_count;

//! Start the video dump process
//! return 1 if video dumping began, else 0
int start_dump_video()
{
  struct tm * tm_current_time;
  time_t time_t_current_time;

  if (video_dump_flag)
    return 0;

  time(&time_t_current_time);
  tm_current_time = localtime(&time_t_current_time);

  snprintf(video_output_base_filename, PATH_MAX, "%svideo-%04d-%02d-%02d %02d-%02d-%02d",
           video_path,
           tm_current_time->tm_year + 1900,
           tm_current_time->tm_mon,
           tm_current_time->tm_mday,
           tm_current_time->tm_hour,
           tm_current_time->tm_min,
           tm_current_time->tm_sec);

  video_output_frame_count = 0;

  // We can't do much in this case, as we don't open a global handle for dumping
  return 1;
}

//! Dump the current frame into the video file
void dump_video_frame()
{
  char *frame_buffer;
  char video_output_filename[PATH_MAX];
  FILE* tmp_video_output_file;

  snprintf(video_output_filename, PATH_MAX, "%s-%d.ppm",
           video_output_base_filename,
           video_output_frame_count);

  frame_buffer = malloc(3 * (io.screen_w & 0xFFFE) * (io.screen_h & 0xFFFE));

  if (frame_buffer == NULL)
    return;

  dump_rgb_frame(frame_buffer);

  tmp_video_output_file = fopen(video_output_filename, "wb");
  if (tmp_video_output_file != NULL)
    {
      char buf[100];

      snprintf(buf, sizeof(buf),
               "P6\n%d %d\n%d\n",
               io.screen_w & 0xFFFE, io.screen_h & 0xFFFE, 255);
      fwrite(buf, strlen(buf), 1, tmp_video_output_file);
      fwrite(frame_buffer, 3 * (io.screen_w & 0xFFFE) * (io.screen_h & 0xFFFE), 1, tmp_video_output_file);
      fclose(tmp_video_output_file);
    }

  video_output_frame_count ++;

  free(frame_buffer);
}

//! Stop the dump video process
void stop_dump_video()
{
  printf("Video dump finished on a %dx%d resolution\n", io.screen_w & 0xFFFE, io.screen_h & 0xFFFE);
}

#else

static FILE* video_output_file = NULL;
static unsigned int old_dump_crc = (unsigned)-1;
static UInt16 old_screen_w, old_screen_h;

static int video_frame_total, video_frame_skipped;

//! Start the video dump process
//! return 1 if video dumping began, else 0
int start_dump_video()
{
  char video_output_filename[PATH_MAX];
  struct tm * tm_current_time;
  time_t time_t_current_time;

  if (video_output_file != NULL)
    return 0;

  (void)time(&time_t_current_time);
  tm_current_time = localtime(&time_t_current_time);

  snprintf(video_output_filename, PATH_MAX, "%svideo-%04d-%02d-%02d %02d-%02d-%02d",
           video_path,
           tm_current_time->tm_year + 1900,
           tm_current_time->tm_mon + 1,
           tm_current_time->tm_mday,
           tm_current_time->tm_hour,
           tm_current_time->tm_min,
           tm_current_time->tm_sec);

  video_output_file = fopen(video_output_filename, "wb");

  old_dump_crc = (unsigned)-1;
  old_screen_w = -1;
  old_screen_h = -1;

  video_frame_total = 0;
  video_frame_skipped = 0;

  return (video_output_file != NULL ? 1 : 0);
}

//! Dump the current frame into the video file
void dump_video_frame()
{
  char* frame_buffer;
  int index;
  unsigned char tmp_data;
  unsigned int CRC = (unsigned)0xFFFFFFFF;

  if (video_output_file == NULL)
    return;

  frame_buffer = malloc((size_t)((io.screen_w & ~1) * (io.screen_h & ~1)));

  if (frame_buffer == NULL)
    return;

  dump_raw_frame(frame_buffer);

  // Compute the rendered buffer CRC
  for (index = 0; index < ((io.screen_w & ~1) * (io.screen_h & ~1)); index++)
    {
      tmp_data = (unsigned char)frame_buffer[index];
      tmp_data ^= CRC;
      CRC >>= 8;
      CRC ^= TAB_CONST[tmp_data];
    }

  if ((old_screen_w != (UInt16)(io.screen_w & ~1)) || (old_screen_h != (UInt16)(io.screen_h & ~1)) || (CRC != old_dump_crc))
    {
      UInt16 dum;

#ifndef NGC
      dum = htons(io.screen_w & ~1);
#else
      dum = (io.screen_w & ~1);
#endif

      (void)fwrite(&dum, 2, 1, video_output_file);

#ifndef NGC
      dum = htons(io.screen_h & ~1);
#else
      dum = (io.screen_h & ~1);
#endif

      (void)fwrite(&dum, 2, 1, video_output_file);

      (void)fwrite(frame_buffer, (size_t)((io.screen_w & ~1) * (io.screen_h & ~1)), 1, video_output_file);

    }
  else
    {
      UInt32 dum;

      dum = 0;

      (void)fwrite(&dum, 4, 1, video_output_file);

      video_frame_skipped ++;
    }

  free(frame_buffer);
  old_screen_h = (UInt16)(io.screen_h & ~1);
  old_screen_w = (UInt16)(io.screen_w & ~1);
  old_dump_crc = CRC;
  video_frame_total ++;
}

//! Stop the dump video process
void stop_dump_video()
{
  char finish_message[100];

  if (video_output_file != NULL)
    fclose(video_output_file);

  snprintf(finish_message, 100, "Video dump finished, %d frames dumped, %d frames compressed (%.2f%% compression)",
           video_frame_total,
           video_frame_skipped,
           (video_frame_total != 0 ? 100.0 * video_frame_skipped / video_frame_total : 100));

  osd_gfx_set_message(finish_message);
}

#endif

#if defined(GFX_DEBUG)
void gfx_debug_printf(char *format, ...)
{
  FILE *log_file;
  char buf[256];

  va_list ap;
  va_start (ap, format);
  vsprintf (buf, format, ap);
  va_end (ap);

  fprintf (stderr, "%3d: ", scanline);
  fprintf (stderr, buf);
  fprintf (stderr, "\n");

  fflush (stderr);
}
#endif
